﻿using System.Threading.Channels;
using Grpc.Core;
using Grpc.Net.Client;
using Logproto;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace LokiProvider;

internal class LokiLoggerProvider: ILoggerProvider, IAsyncDisposable
{
    private IOptions<LokiLoggerOptions> _options;
    private Channel<StreamAdapter> _channel;
    private TaskCompletionSource _completeTcs = new ();
    private GrpcChannel _grpc;
    private Pusher.PusherClient _client;

    public LokiLoggerProvider(IOptions<LokiLoggerOptions> options)
    {
        _options = options;
        
        _grpc = GrpcChannel.ForAddress(_options.Value.Address, new GrpcChannelOptions
        {
            HttpClient = new HttpClient()
            {
                DefaultRequestVersion = new Version(2, 0)
            },
            Credentials = ChannelCredentials.Insecure,
        });
        _client = new Pusher.PusherClient(_grpc);
        
        if (_options.Value.Async)
        {
            _channel = Channel.CreateBounded<StreamAdapter>(new BoundedChannelOptions(256));
            _ = Run();
        }
    }
    
    private async Task Run()
    {
        try
        {

            while (true)
            {
                PushRequest req = new PushRequest();

                if (!await _channel.Reader.WaitToReadAsync())
                {
                    break;
                }

                while (_channel.Reader.TryRead(out var item))
                {
                    req.Streams.Add(item);
                }

                if (req.Streams.Count > 0)
                {
                    try
                    {
                        await _client.PushAsync(req);
                    }
                    catch (Exception e)
                    {
                        System.Console.Error.WriteLine(e);
                    }
                }
            }
        }
        finally
        {
            _completeTcs.SetResult();
        }
    }
    
    public void Dispose()
    {
        DisposeAsync().AsTask().Wait();
    }
    
    public async ValueTask DisposeAsync()
    {
        if (_options.Value.Async)
        {
            _channel.Writer.Complete();
            await _completeTcs.Task;
        }
        _grpc.Dispose();
    }

    public ILogger CreateLogger(string categoryName)
    {
        return new LokiLogger(this, _options.Value.App, categoryName);
    }

    public void Add(StreamAdapter s)
    {
        if (_options.Value.Async)
        {
            _channel.Writer.TryWrite(s);
        }
        else
        {
            var req = new PushRequest();
            req.Streams.Add(s);
            _client.Push(req);
        }
    }
}